// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2021 Huawei Technologies Co., Ltd */

#define pr_fmt(fmt) KBUILD_MODNAME ": [IPsec]" fmt

#include <linux/inetdevice.h>
#include <linux/netdevice.h>
#include <linux/module.h>
#include <net/xfrm.h>
#include <net/esp.h>
#include <crypto/aead.h>

#include "ossl_knl.h"
#include "hinic3_hw.h"
#include "hinic3_crm.h"
#include "hinic3_nic_dev.h"

#include "hisec_cfg.h"
#include "hisec_common.h"
#include "hinic3_tx.h"
#include "hinic3_ipsec.h"

unsigned char ipsec_work_mode = HISEC_IPSEC_OFFLOAD_MODE_DISABLE;
module_param(ipsec_work_mode, byte, 0444);
MODULE_PARM_DESC(ipsec_work_mode, "IPsec work mode:(0-disable 1-xfrm offload 2-hwacc)");

#define HINIC3_IPSEC_STATS_INC(ipsec, field)		\
do {							\
	u64_stats_update_begin(&(ipsec)->stats.syncp);	\
	(ipsec)->stats.field++;				\
	u64_stats_update_end(&(ipsec)->stats.syncp);	\
} while (0)

static void hinic3_ipsec_clean_stats(struct hinic3_ipsec_stats *ipsec_stats)
{
	u64_stats_update_begin(&ipsec_stats->syncp);
	memset(ipsec_stats, 0,
	       sizeof(*ipsec_stats) - sizeof(ipsec_stats->syncp));
	u64_stats_update_end(&ipsec_stats->syncp);
}

static void hinic3_ipsec_init_stats(struct hinic3_ipsec *ipsec)
{
	struct hinic3_ipsec_stats *ipsec_stats = &ipsec->stats;

	u64_stats_init(&ipsec_stats->syncp);
	hinic3_ipsec_clean_stats(ipsec_stats);
}

int hinic3_init_ipsec_offload(struct hinic3_nic_dev *nic_dev)
{
	struct hinic3_ipsec *ipsec = NULL;
	struct hinic3_ipsec_xmap *xmap = NULL;
	u32 xmap_size;
	int ret;
	u16 glb_func_id;

	if (!hinic3_support_ipsec(nic_dev->hwdev, NULL)) {
		nic_dev->ipsec = NULL;
		nic_info(&nic_dev->pdev->dev, "Hw don't support ipsec\n");
		return 0;
	}

	ret = hinic3_stateful_init(nic_dev->hwdev);
	if (ret) {
		nic_info(&nic_dev->pdev->dev, "init statefull failed");
		return ret;
	}

	glb_func_id = hinic3_global_func_id(nic_dev->hwdev);
	ret = hinic3_func_reset(nic_dev->hwdev, glb_func_id, HINIC3_IPSEC_RES,
				HINIC3_CHANNEL_CRYPT);
	if (ret) {
		nic_err(&nic_dev->pdev->dev, "Failed to reset IPsec function:%d\n",
			ret);
		goto func_reset_fail;
	}

	ipsec = kzalloc(sizeof(*ipsec), GFP_KERNEL);
	if (!ipsec) {
		ret = -ENOMEM;
		nic_err(&nic_dev->pdev->dev, "Alloc ipsec mem failed\n");
		goto alloc_ipsec_fail;
	}

	/* xid 0 & 1 internal used by CQM */
	xmap_size = sizeof(*xmap) * (HINIC3_IPSEC_PER_PF_SA_CTX_NUM +
				     HINIC3_IPSEC_XID_RESERVE_NUM);
	xmap = kzalloc(xmap_size, GFP_KERNEL);
	if (!xmap) {
		ret = -ENOMEM;
		nic_err(&nic_dev->pdev->dev, "Alloc xmap mem failed\n");
		goto alloc_xmap_fail;
	}
	ipsec->xmap = xmap;

	hash_init(ipsec->sadb);
	spin_lock_init(&ipsec->sadb_lock);

	ipsec->nic_dev = nic_dev;
	nic_dev->ipsec = ipsec;
	ipsec->no_trailer = true; /* ESP trailer always removed by hw */

	hinic3_ipsec_init_stats(ipsec);

	ret = hisec_set_ipsec_offload_mode(nic_dev->hwdev, ipsec_work_mode);
	if (ret) {
		nic_err(&nic_dev->pdev->dev,
			"Set IPsec offload mode failed: %d\n", ret);
		goto set_mode_fail;
	}

	nic_info(&nic_dev->pdev->dev,
		 "Init IPsec offload mode(0x%x) success\n", ipsec_work_mode);

	return ret;

set_mode_fail:
	kfree(ipsec->xmap);
alloc_xmap_fail:
	kfree(ipsec);
	nic_dev->ipsec = NULL;
alloc_ipsec_fail:
func_reset_fail:
	hinic3_stateful_deinit(nic_dev->hwdev);
	return ret;
}

void hinic3_cleanup_ipsec_offload(struct hinic3_nic_dev *nic_dev)
{
	struct hinic3_ipsec *ipsec = nic_dev->ipsec;
	int ret;

	if (!hinic3_support_ipsec(nic_dev->hwdev, NULL))
		return;

	if (!ipsec) {
		nic_err(&nic_dev->pdev->dev, "Param ipsec is NULL\n");
		return;
	}

	ret = hisec_set_ipsec_offload_mode(nic_dev->hwdev, 0);
	if (ret) {
		nic_err(&nic_dev->pdev->dev,
			"Set IPsec offload mode failed: %d\n", ret);
	}

	ret = hisec_flush_ipsec_resource(nic_dev->hwdev, HISEC_IPSECRES_ALL);
	if (ret) {
		nic_err(&nic_dev->pdev->dev, "Failed to flush IPsec res:%d\n",
			ret);
	}

	kfree(ipsec->xmap);
	kfree(ipsec);
	nic_dev->ipsec = NULL;

	hinic3_stateful_deinit(nic_dev->hwdev);

	nic_info(&nic_dev->pdev->dev, "Cleanup IPsec offload success\n");
}
